using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
using System.Diagnostics;
using Polenter.Serialization;
using Polenter.Serialization.Core;
using Polenter.Serialization.Advanced.Serializing;
using AbstraX.BuildEvents;

namespace AbstraX
{
    internal enum DataType
    {
        StringType,
        IntegerType,
        FloatType,
        ExceptionType,
        BuildEventArgsType 
    } 

    internal class TypeNameConverter : ITypeNameConverter
    {
        public Type ConvertToType(string typeName)
        {
            if (typeName == "AbstraX.RemotableException")
            {
                return typeof(RemotableException);
            }
            else if (typeName == "AbstraX.BuildEvents.BuildErrorEventArgs")
            {
                return typeof(BuildErrorEventArgs);
            }
            else if (typeName == "AbstraX.BuildEvents.BuildFinishedEventArgs")
            {
                return typeof(BuildFinishedEventArgs);
            }
            else if (typeName == "AbstraX.BuildEvents.BuildMessageEventArgs")
            {
                return typeof(BuildMessageEventArgs);
            }
            else if (typeName == "AbstraX.BuildEvents.BuildStartedEventArgs")
            {
                return typeof(BuildStartedEventArgs);
            }
            else if (typeName == "AbstraX.BuildEvents.BuildStatusEventArgs")
            {
                return typeof(BuildStatusEventArgs);
            }
            else if (typeName == "AbstraX.BuildEvents.BuildWarningEventArgs")
            {
                return typeof(BuildWarningEventArgs);
            }
            else if (typeName == "AbstraX.BuildEvents.CustomBuildEventArgs")
            {
                return typeof(CustomBuildEventArgs);
            }
            else if (typeName == "AbstraX.BuildEvents.ProjectFinishedEventArgs")
            {
                return typeof(ProjectFinishedEventArgs);
            }
            else if (typeName == "AbstraX.BuildEvents.ProjectStartedEventArgs")
            {
                return typeof(ProjectStartedEventArgs);
            }
            else if (typeName == "AbstraX.BuildEvents.TargetFinishedEventArgs")
            {
                return typeof(TargetFinishedEventArgs);
            }
            else if (typeName == "AbstraX.BuildEvents.TargetStartedEventArgs")
            {
                return typeof(TargetStartedEventArgs);
            }
            else if (typeName == "AbstraX.BuildEvents.TaskFinishedEventArgs")
            {
                return typeof(TaskFinishedEventArgs);
            }
            else if (typeName == "AbstraX.BuildEvents.TaskStartedEventArgs")
            {
                return typeof(TaskStartedEventArgs);
            }

            if (typeName != null)
            {
                return Type.GetType(typeName);
            }
            else
            {
                return null;
            }
        }

        public string ConvertToTypeName(Type type)
        {
            return type.FullName;
        }
    }

    internal static class EventMessageHelper
    {
        private const int PARM_COUNT_WIDTH = 2;

        internal static string GetObjectBytes(object obj)
        {
            var settings = new SharpSerializerXmlSettings
            {
                AdvancedSettings = new AdvancedSharpSerializerXmlSettings
                {
                    TypeNameConverter = new TypeNameConverter()
                },

                IncludeAssemblyVersionInTypeName = false,
                IncludeCultureInTypeName = false,
                IncludePublicKeyTokenInTypeName = false
            };

            var serializer = new SharpSerializer(settings);
            var stream = new MemoryStream();
            var text = string.Empty;
            var bytes = (byte[])null;

            serializer.Serialize(obj, stream);

            stream.Seek(0, SeekOrigin.Begin);

            bytes = new byte[stream.Length];

            stream.Read(bytes, 0, (int)stream.Length);

#if !SILVERLIGHT
            text = ASCIIEncoding.ASCII.GetString(bytes);
#endif
            text = Convert.ToBase64String(bytes);

            return text;
        }

        internal static string GetExceptionBytes(Exception ex)
        {
            return GetExceptionBytes(ex, ex.StackTrace);
        }

        internal static string GetExceptionBytes(Exception ex, string stackTrace)
        {
            var settings = new SharpSerializerXmlSettings
            {
                AdvancedSettings = new AdvancedSharpSerializerXmlSettings
                {
                    TypeNameConverter = new TypeNameConverter()
                },

                IncludeAssemblyVersionInTypeName = false,
                IncludeCultureInTypeName = false,
                IncludePublicKeyTokenInTypeName = false
            };

            var serializer = new SharpSerializer(settings);
            var stream = new MemoryStream();
            var text = string.Empty;
            var bytes = (byte[])null;
            var ex2 = new RemotableException(ex);

            if (ex2.StackTrace == null)
            {
                ex2.StackTrace = stackTrace;
            }

            serializer.Serialize(ex2, stream);

            stream.Seek(0, SeekOrigin.Begin);

            bytes = new byte[stream.Length];

            stream.Read(bytes, 0, (int)stream.Length);

#if !SILVERLIGHT
            text = ASCIIEncoding.ASCII.GetString(bytes);
#endif
            text = Convert.ToBase64String(bytes);

            return text;
        }

        internal static void WriteTo(this EventMessage eventMessage, Message message, params object[] parms)
        {
            var stream = new MemoryStream();
            var writer = new BinaryWriter(stream);

            // first int is number of parms

            writer.Write(parms.Length);

            eventMessage.Message = message;

            // order: datatype, length, data

            foreach (var parm in parms)
            {
                if (parm is BuildEventArgs)
                {
                    var value = GetObjectBytes(parm);

                    writer.Write((int)DataType.BuildEventArgsType);
                    writer.Write(value.Length + 1);
                    writer.Write(value);
                }
                else if (parm is Exception)
                {
                    if (parms.Count() > 1)
                    {
                        var stackTrace = (string)parms.ElementAt(1);
                        var value = GetExceptionBytes((Exception)parm, stackTrace);

                        writer.Write((int)DataType.ExceptionType);
                        writer.Write(value.Length + 1);
                        writer.Write(value);
                    }
                    else
                    {
                        var value = GetExceptionBytes((Exception)parm);

                        writer.Write((int)DataType.ExceptionType);
                        writer.Write(value.Length + 1);
                        writer.Write(value);
                    }
                }
                else
                {
                    switch (parm.GetType().Name.ToLower())
                    {
                        case "string":
                            {
                                var value = (string)parm;

                                writer.Write((int)DataType.StringType);
                                writer.Write(value.Length + 1);
                                writer.Write(value);

                                break;
                            }
                        case "int32":
                            {
                                var value = (int)parm;

                                writer.Write((int)DataType.IntegerType);
                                writer.Write(4);
                                writer.Write(value);

                                break;
                            }
                        case "single":
                            {
                                var value = (float)parm;

                                writer.Write((int)DataType.FloatType);
                                writer.Write(4);
                                writer.Write(value);

                                break;
                            }

                        default:
                            Debugger.Break();
                            break;
                    }
                }
            }

            writer.Flush();

            eventMessage.EventArgs = new byte[(int)stream.Length];

            stream.Seek(0, SeekOrigin.Begin);
            stream.Read(eventMessage.EventArgs, 0, (int)stream.Length);

            writer.Close();
            stream.Close();
        }

        internal static object[] ReadFrom(this EventMessage eventMessage)
        {
            var stream = new MemoryStream(eventMessage.EventArgs.Length);
            var reader = new BinaryReader(stream);
            var count = 0;
            object[] messageObjects;

            stream.Write(eventMessage.EventArgs, 0, eventMessage.EventArgs.Length);
            stream.Seek(0, SeekOrigin.Begin);

            count = reader.ReadInt32();
            messageObjects = new object[count];

            for (var x = 0; x < count; x++)
            {
                var dataType = (DataType)reader.ReadInt32();
                var length = reader.ReadInt32();

                switch (dataType)
                {
                    case DataType.BuildEventArgsType:
                        {
                            var settings = new SharpSerializerXmlSettings
                            {
                                AdvancedSettings = new AdvancedSharpSerializerXmlSettings
                                {
                                    TypeNameConverter = new TypeNameConverter()
                                }
                            };

                            var serializer = new SharpSerializer(settings);
                            var memoryStream = new MemoryStream();
                            var bytes = (byte[])null;
                            var value = reader.ReadString();
                            var streamReader = new StreamReader(memoryStream);

                            bytes = Convert.FromBase64String(value);

                            memoryStream.Write(bytes, 0, bytes.Length);
                            memoryStream.Seek(0, SeekOrigin.Begin);

                            value = streamReader.ReadToEnd();

                            memoryStream.Seek(0, SeekOrigin.Begin);

                            messageObjects[x] = serializer.Deserialize(memoryStream);
                        }

                        break;

                    case DataType.ExceptionType:
                        {
                            var settings = new SharpSerializerXmlSettings
                            { 
                                AdvancedSettings = new AdvancedSharpSerializerXmlSettings
                                {
                                    TypeNameConverter = new TypeNameConverter(),
                                }
                            };

                            var serializer = new SharpSerializer(settings);
                            var memoryStream = new MemoryStream();
                            var bytes = (byte[])null;
                            var value = reader.ReadString();
                            var streamReader = new StreamReader(memoryStream);

                            bytes = Convert.FromBase64String(value);

                            memoryStream.Write(bytes, 0, bytes.Length);
                            memoryStream.Seek(0, SeekOrigin.Begin);

                            value = streamReader.ReadToEnd();

                            memoryStream.Seek(0, SeekOrigin.Begin); 

                            messageObjects[x] = (RemotableException)serializer.Deserialize(memoryStream);
                        } 

                        break;

                    case DataType.StringType:

                        messageObjects[x] = reader.ReadString();
                        break;

                    case DataType.IntegerType:

                        messageObjects[x] = reader.ReadInt32();
                        break;

                    case DataType.FloatType:

                        messageObjects[x] = reader.ReadSingle();
                        break;
                }
            } 

            reader.Close(); 
            stream.Close(); 

            return messageObjects;
        }
    }
}
